////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNCore/inc/WNAssert.h"
#include "WNMath/inc/WNVector2.h"
#include "WNMath/inc/WNBasicMath.h"
#include "WNMath/inc/WNExtendedMath.h"
#include "WNMath/inc/WNPoint2.h"
#include "WNMath/inc/WNPoint3.h"
#include "WNMath/inc/WNRay2.h"
#include "WNMath/inc/WNRay3.h"
#include "WNMath/inc/WNVector3.h"

namespace WNMath {
    WNVector2 operator + (WN_FLOAT32 _f, const WNVector2& _vector) {
        return(WNVector2(_f + _vector.x, _f + _vector.y));
    }

    WNVector2 operator - (WN_FLOAT32 _f, const WNVector2& _vector) {
        return(WNVector2(_f - _vector.x, _f - _vector.y));
    }

    WNVector2 operator * (WN_FLOAT32 _f, const WNVector2& _vector) {
        return(WNVector2(_f * _vector.x, _f * _vector.y));
    }

    WNVector2 operator / (WN_FLOAT32 _f, const WNVector2& _vector) {
        const WN_FLOAT32 fInv = 1.0f / _f;

        return(WNVector2(fInv * _vector.x, fInv * _vector.y));
    }
}

using namespace WNMath;

// Constructors ////////////////////////////////////////////////////////////////////////////////////

WNVector2::WNVector2() :
    x(0.0f),
    y(0.0f) {
}

WNVector2::WNVector2(WN_FLOAT32 _f) :
    x(_f),
    y(_f) {
}

WNVector2::WNVector2(const WN_FLOAT32* _f) :
    x(_f[0]),
    y(_f[1]) {
}

WNVector2::WNVector2(WN_FLOAT32 _x, WN_FLOAT32 _y) :
    x(_x),
    y(_y) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////////

WNVector2::operator WN_FLOAT32* () {
    return(&x);
}

WNVector2::operator const WN_FLOAT32* () const {
    return(&x);
}

WN_FLOAT32& WNVector2::operator () (WN_INT32 _index) {
    WN_DEBUG_ASSERT(_index < 2);

    return(*(&x + _index));
}

WN_FLOAT32 WNVector2::operator () (WN_INT32 _index) const {
    WN_DEBUG_ASSERT(_index < 2);

    return(*(&x + _index));
}

WNVector2& WNVector2::operator += (const WNVector2& _vector) {
    x += _vector.x;
    y += _vector.y;

    return(*this);
}

WNVector2& WNVector2::operator -= (const WNVector2& _vector) {
    x -= _vector.x;
    y -= _vector.y;

    return(*this);
}

WNVector2& WNVector2::operator *= (const WNVector2& _vector) {
    x *= _vector.x;
    y *= _vector.y;

    return(*this);
}

WNVector2& WNVector2::operator += (WN_FLOAT32 _f) {
    x += _f;
    y += _f;

    return(*this);
}

WNVector2& WNVector2::operator -= (WN_FLOAT32 _f) {
    x -= _f;
    y -= _f;

    return(*this);
}

WNVector2& WNVector2::operator *= (WN_FLOAT32 _f) {
    x *= _f;
    y *= _f;

    return(*this);
}

WNVector2& WNVector2::operator /= (WN_FLOAT32 _f) {
    const WN_FLOAT32 fInv = 1.0f / _f;

    x *= fInv;
    y *= fInv;

    return(*this);
}

WNVector2 WNVector2::operator + () const {
    return(*this);
}

WNVector2 WNVector2::operator - () const {
    return(WNVector2(-x, -y));
}

WNVector2 WNVector2::operator + (const WNVector2& _vector) const {
    return(WNVector2(x + _vector.x, y + _vector.y));
}

WNVector2 WNVector2::operator - (const WNVector2& _vector) const {
    return(WNVector2(x - _vector.x, y - _vector.y));
}

WNVector2 WNVector2::operator * (const WNVector2& _vector) const {
    return(WNVector2(x * _vector.x, y * _vector.y));
}

WNVector2 WNVector2::operator + (WN_FLOAT32 _f) const {
    return(WNVector2(x + _f, y + _f));
}

WNVector2 WNVector2::operator - (WN_FLOAT32 _f) const {
    return(WNVector2(x - _f, y - _f));
}

WNVector2 WNVector2::operator * (WN_FLOAT32 _f) const {
    return(WNVector2(x * _f, y * _f));
}

WNVector2 WNVector2::operator / (WN_FLOAT32 _f) const {
    const WN_FLOAT32 fInv = 1.0f / _f;

    return(WNVector2(x * fInv, y * fInv));
}

WN_BOOL WNVector2::operator == (const WNVector2& _vector) const {
    return(0 == WNMemCmp(this, &_vector, sizeof(WNVector2)));
}

WN_BOOL WNVector2::operator != (const WNVector2& _vector) const {
    return(0 != WNMemCmp(this, &_vector, sizeof(WNVector2)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNVector2::Zero() {
    WNMemSet(&x, 0, sizeof(WNVector2));
}

WN_VOID WNVector2::Set(const WN_FLOAT32* _f) {
    WNMemCpy(&x, &_f[0], sizeof(WNVector2));
}

WN_VOID WNVector2::Set(WN_FLOAT32 _x, WN_FLOAT32 _y) {
    x = _x;
    y = _y;
}

WN_BOOL WNVector2::IsZero() const {
    return(x == 0.0f && y == 0.0f);
}

WN_VOID WNVector2::Normalize() {
    const WN_FLOAT32 invLength = WNInvSqrt(LengthSquared());

    *this *= invLength;
}

WN_VOID WNVector2::Truncate(WN_FLOAT32 _length) {
    if (_length != 0.0f) {
        const WN_FLOAT32 lengthSquared = LengthSquared();

        if (lengthSquared > (_length * _length)) {
            const WN_FLOAT32 invLength = _length * WNInvSqrt(lengthSquared);

            *this *= invLength;
        }
    } else {
        Zero();
    }
}

WN_VOID WNVector2::Clamp(const WNVector2& _min, const WNVector2& _max) {
    const WNVector2 vec = *this;
    const WNVector2 vecMin = _min;
    const WNVector2 vecMax = _max;

    if (vec.x > vecMax.x) {
        x = vecMax.x;
    } else if (vec.x < vecMin.x) {
        x = vecMin.x;
    }

    if (vec.y > vecMax.y) {
        y = vecMax.y;
    } else if (vec.y < vecMin.y) {
        y = vecMin.y;
    }
}

WN_VOID WNVector2::Snap(WNSnapDirection _direction) {
    const WNVector2 vec = *this;

    switch(_direction) {
    case WN_SNAP_NEAREST:
        x = WNRound(vec.x);
        y = WNRound(vec.y);

        break;
    case WN_SNAP_UP:
        x = WNCeil(vec.x);
        y = WNCeil(vec.y);

        break;
    case WN_SNAP_DOWN:
        x = WNFloor(vec.x);
        y = WNFloor(vec.y);

        break;
    case WN_SNAP_TRUNCATE:
        x = WNTrunc(vec.x);
        y = WNTrunc(vec.y);
    }
}

WN_VOID WNVector2::Reflect(const WNVector2& _normal) {
    const WNVector2 vec = *this;

    *this = (vec - (2.0f * _normal * WNDot(vec, _normal)));
}

WN_VOID WNVector2::Rotate(WN_FLOAT32 _angle) {
    const WNVector2 vec = *this;

    x = vec.x * WNCos(_angle) - vec.y * WNSin(_angle);
    y = vec.y * WNCos(_angle) + vec.x * WNSin(_angle);
}

WN_VOID WNVector2::Scale(WN_FLOAT32 _scale) {
    *this *= _scale;
}

WN_VOID WNVector2::Scale(WN_FLOAT32 _x, WN_FLOAT32 _y) {
    x *= _x;
    y *= _y;
}
    
WN_VOID WNVector2::Scale(const WNVector2& _scale) {
    x *= _scale.x;
    y *= _scale.y;
}

WN_FLOAT32 WNVector2::Length() const {
    const WNVector2 vec = *this;

    return(WNSqrt((vec.x * vec.x) + (vec.y * vec.y)));
}

WN_FLOAT32 WNVector2::LengthSquared() const {
    const WNVector2 vec = *this;

    return((vec.x * vec.x) + (vec.y * vec.y));
}

WNVector2 WNVector2::GetNormalized() const {
    WNVector2 vec = *this;

    return(vec.Normalize(), vec);
}

WNVector2 WNVector2::GetTruncated(WN_FLOAT32 _length) const {
    WNVector2 vec = *this;

    return(vec.Truncate(_length), vec);
}

WNVector2 WNVector2::GetClamped(const WNVector2& _min, const WNVector2& _max) const {
    WNVector2 vec = *this;

    return(vec.Clamp(_min, _max), vec);
}

WNVector2 WNVector2::GetSnaped(WNSnapDirection _direction) const {
    WNVector2 vec = *this;

    return(vec.Snap(_direction), vec);
}

WNVector2 WNVector2::GetReflected(const WNVector2& _normal) const {
    WNVector2 vec = *this;

    return(vec.Reflect(_normal), vec);
}

WNVector2 WNVector2::GetRotated(WN_FLOAT32 _angle) const {
    WNVector2 vec = *this;

    return(vec.Rotate(_angle), vec);
}

WNVector2 WNVector2::GetScaled(WN_FLOAT32 _scale) const {
    WNVector2 vec = *this;

    return(vec.Scale(_scale), vec);
}

WNVector2 WNVector2::GetScaled(WN_FLOAT32 _x, WN_FLOAT32 _y) const {
    WNVector2 vec = *this;

    return(vec.Scale(_x, _y), vec);
}

WNVector2 WNVector2::GetScaled(const WNVector2& _scale) const {
    WNVector2 vec = *this;

    return(vec.Scale(_scale), vec);
}

WNPoint2 WNVector2::ToPoint2() const {
    return(WNPoint2(x, y));
}

WNPoint3 WNVector2::ToPoint3() const {
    return(WNPoint3(x, y, 0.0f));
}

WNRay2 WNVector2::ToRay2() const {
    return(WNRay2(WNPoint2(), *this));
}

WNRay2 WNVector2::ToRay2(const WNPoint2& _location) const {
    return(WNRay2(_location, *this));
}

WNRay3 WNVector2::ToRay3() const {
    return(WNRay3(WNPoint3(), (*this).ToVector3()));
}

WNRay3 WNVector2::ToRay3(const WNPoint3& _location) const {
    return(WNRay3(_location, (*this).ToVector3()));
}

WNVector3 WNVector2::ToVector3() const {
    return(WNVector3(x, y, 0.0f));
}